REMEMBER TO COMMENT AS YOU GO!!!!!!!! SETWD WITH GUI

library(biomaRt)
library(DESeq2)
library(tidyverse)

Before starting this section, we will make sure we have all the relevant objects from the Differential Expression analysis.

load("../Course_Materials/Robjects/DE.RData")

Overview

Adding annotation to the DESeq2 results

VIEW resLvV only see the Ensembl Gene ID, which is not very informative.

There are a number of ways to add annotation. The are R packages for this at an organism level which are updated every 6 months.

An alternative approach is to use biomaRt, an interface to the BioMart resource. This is the method we will use today.

Select BioMart database and dataset

The first step is to select the Biomart database we are going to access and which data set we are going to use.

Explain 4 marts

# view the available databases
listMarts()
## set up connection to ensembl database
ensembl=useMart("ENSEMBL_MART_ENSEMBL")
# list the available datasets (species)
listDatasets(ensembl) %>% 
    filter(str_detect(description, "Mouse"))
# specify a data set to use
ensembl = useDataset("mmusculus_gene_ensembl", mart=ensembl)

Query the database

set up query

  • filters - what we search with - Ensembl Gene IDs
  • values - where they are - the Ensembl Gene IDs from our DE results table
  • attributes - what we want back

  • test your query on a small list as it takes a while for the whole lot

# check the available "filters" - things you can filter for
listFilters(ensembl) %>% 
    filter(str_detect(name, "ensembl"))
# Set the filter type and values
ourFilterType <- "ensembl_gene_id"
filterValues <- rownames(resLvV)[1:1000]
# check the available "attributes" - things you can retreive
listAttributes(ensembl) %>% 
    head(20)
# Set the list of attributes
attributeNames <- c('ensembl_gene_id', 'entrezgene', 'external_gene_name')
# run the query
annot <- getBM(attributes=attributeNames, 
               filters = ourFilterType, 
               values = filterValues, 
               mart = ensembl)

Batch submitting query [==============================>---------------]  67% eta:  0s
Batch submitting query [==============================================] 100% eta:  0s
                                                                                     

One-to-many relationships

Let’s inspect the annotation.

head(annot)
dim(annot) # why are there more than 1000 rows?
[1] 1001    3
length(unique(annot$ensembl_gene_id)) # why are there less than 1000 Gene ids?
[1] 999
isDup <- duplicated(annot$ensembl_gene_id)
dup <- annot$ensembl_gene_id[isDup]
annot[annot$ensembl_gene_id%in%dup,]

missing one is depreceated gene annotation… (our gtf is little older than biomaRt)

There are a couple of genes that have multiple entries in the retrieved annotation. This is becaues there are multiple Entrez IDs for a single Ensembl gene. These one-to-many relationships come up frequently in genomic databases, it is important to be aware of them and check when necessary.

We will need to do a little work before adding the annotation to out results table. We could decide to discard one or both of the Entrez ID mappings, or we could concatenate the Entrez IDs so that we don’t lose information.

this illustrates how/why annotation is complicated and difficult

Retrieve full annotation

Challenge 1

That was just 1000 genes. We need annotations for the entire results table. Also, there may be some other interesting columns in BioMart that we wish to retrieve.

  1. Search the attributes and add the following to our list of attributes:
    1. The gene description
    2. The gene biotype
  2. Query BioMart using all of the genes in our results table (resLvV)

  3. How many Ensembl genes have multipe Entrez IDs associated with them?
  4. How many Ensembl genes in resLvV don’t have any annotation? Why is this?

filterValues <- rownames(resLvV)
# check the available "attributes" - things you can retreive
listAttributes(ensembl) %>%
    head(20)
attributeNames <- c('ensembl_gene_id',
                    'entrezgene',
                    'external_gene_name',
                    'description',
                    'gene_biotype')
# run the query
annot <- getBM(attributes=attributeNames,
               filters = ourFilterType,
               values = filterValues,
               mart = ensembl)

Batch submitting query [=>--------------------------------------------]   4% eta:  6s
Batch submitting query [==>-------------------------------------------]   7% eta:  7s
Batch submitting query [===>------------------------------------------]   9% eta:  7s
Batch submitting query [====>-----------------------------------------]  11% eta:  8s
Batch submitting query [=====>----------------------------------------]  13% eta:  8s
Batch submitting query [======>---------------------------------------]  16% eta:  8s
Batch submitting query [=======>--------------------------------------]  18% eta:  8s
Batch submitting query [========>-------------------------------------]  20% eta:  8s
Batch submitting query [=========>------------------------------------]  22% eta:  8s
Batch submitting query [==========>-----------------------------------]  24% eta:  8s
Batch submitting query [===========>----------------------------------]  27% eta:  8s
Batch submitting query [============>---------------------------------]  29% eta:  8s
Batch submitting query [=============>--------------------------------]  31% eta:  7s
Batch submitting query [==============>-------------------------------]  33% eta:  7s
Batch submitting query [===============>------------------------------]  36% eta:  7s
Batch submitting query [================>-----------------------------]  38% eta:  6s
Batch submitting query [=================>----------------------------]  40% eta:  7s
Batch submitting query [==================>---------------------------]  42% eta: 11s
Batch submitting query [===================>--------------------------]  44% eta: 11s
Batch submitting query [====================>-------------------------]  47% eta: 11s
Batch submitting query [=====================>------------------------]  49% eta: 11s
Batch submitting query [=======================>----------------------]  51% eta: 10s
Batch submitting query [========================>---------------------]  53% eta: 10s
Batch submitting query [=========================>--------------------]  56% eta:  9s
Batch submitting query [==========================>-------------------]  58% eta:  8s
Batch submitting query [===========================>------------------]  60% eta:  8s
Batch submitting query [============================>-----------------]  62% eta:  7s
Batch submitting query [=============================>----------------]  64% eta:  7s
Batch submitting query [==============================>---------------]  67% eta:  6s
Batch submitting query [===============================>--------------]  69% eta:  6s
Batch submitting query [================================>-------------]  71% eta:  5s
Batch submitting query [=================================>------------]  73% eta:  5s
Batch submitting query [==================================>-----------]  76% eta:  4s
Batch submitting query [===================================>----------]  78% eta:  4s
Batch submitting query [====================================>---------]  80% eta:  3s
Batch submitting query [=====================================>--------]  82% eta:  3s
Batch submitting query [======================================>-------]  84% eta:  3s
Batch submitting query [=======================================>------]  87% eta:  2s
Batch submitting query [========================================>-----]  89% eta:  2s
Batch submitting query [=========================================>----]  91% eta:  1s
Batch submitting query [==========================================>---]  93% eta:  1s
Batch submitting query [===========================================>--]  96% eta:  1s
Batch submitting query [============================================>-]  98% eta:  0s
Batch submitting query [==============================================] 100% eta:  0s
                                                                                     
# dulicate ids
sum(duplicated(annot$ensembl_gene_id))
[1] 63
# missing gens
missingGenes <- !rownames(resLvV)%in%annot$ensembl_gene_id
rownames(resLvV)[missingGenes]
  [1] "ENSMUSG00000104475" "ENSMUSG00000089853" "ENSMUSG00000089788" "ENSMUSG00000104003"
  [5] "ENSMUSG00000079537" "ENSMUSG00000083797" "ENSMUSG00000087594" "ENSMUSG00000087503"
  [9] "ENSMUSG00000086605" "ENSMUSG00000081401" "ENSMUSG00000102426" "ENSMUSG00000106055"
 [13] "ENSMUSG00000086864" "ENSMUSG00000087349" "ENSMUSG00000096368" "ENSMUSG00000078484"
 [17] "ENSMUSG00000053656" "ENSMUSG00000097198" "ENSMUSG00000029333" "ENSMUSG00000070632"
 [21] "ENSMUSG00000044060" "ENSMUSG00000085455" "ENSMUSG00000084894" "ENSMUSG00000073090"
 [25] "ENSMUSG00000085341" "ENSMUSG00000060393" "ENSMUSG00000050604" "ENSMUSG00000003178"
 [29] "ENSMUSG00000056872" "ENSMUSG00000087297" "ENSMUSG00000063757" "ENSMUSG00000078384"
 [33] "ENSMUSG00000099697" "ENSMUSG00000100370" "ENSMUSG00000100214" "ENSMUSG00000101458"
 [37] "ENSMUSG00000043858" "ENSMUSG00000052429" "ENSMUSG00000038194" "ENSMUSG00000085698"
 [41] "ENSMUSG00000085214" "ENSMUSG00000084882" "ENSMUSG00000085186" "ENSMUSG00000098496"
 [45] "ENSMUSG00000082509" "ENSMUSG00000097810" "ENSMUSG00000085583" "ENSMUSG00000063254"
 [49] "ENSMUSG00000101176" "ENSMUSG00000100181" "ENSMUSG00000100855" "ENSMUSG00000089672"
 [53] "ENSMUSG00000078441" "ENSMUSG00000087481" "ENSMUSG00000094614" "ENSMUSG00000065950"
 [57] "ENSMUSG00000097922" "ENSMUSG00000060559" "ENSMUSG00000086883" "ENSMUSG00000095306"
 [61] "ENSMUSG00000090108" "ENSMUSG00000057924" "ENSMUSG00000071083" "ENSMUSG00000072432"
 [65] "ENSMUSG00000100612" "ENSMUSG00000103515" "ENSMUSG00000093760" "ENSMUSG00000071567"
 [69] "ENSMUSG00000081850" "ENSMUSG00000102171" "ENSMUSG00000091089" "ENSMUSG00000092443"
 [73] "ENSMUSG00000102798" "ENSMUSG00000104389" "ENSMUSG00000079489" "ENSMUSG00000097243"
 [77] "ENSMUSG00000032134" "ENSMUSG00000102340" "ENSMUSG00000097298" "ENSMUSG00000102183"
 [81] "ENSMUSG00000105383" "ENSMUSG00000101716" "ENSMUSG00000082946" "ENSMUSG00000084141"
 [85] "ENSMUSG00000091095" "ENSMUSG00000086394" "ENSMUSG00000086772" "ENSMUSG00000086508"
 [89] "ENSMUSG00000064168" "ENSMUSG00000096252" "ENSMUSG00000051107" "ENSMUSG00000059511"
 [93] "ENSMUSG00000103283" "ENSMUSG00000096789" "ENSMUSG00000071193" "ENSMUSG00000094518"
 [97] "ENSMUSG00000094114" "ENSMUSG00000095709" "ENSMUSG00000094947" "ENSMUSG00000095406"
[101] "ENSMUSG00000035349" "ENSMUSG00000099597" "ENSMUSG00000101767" "ENSMUSG00000097058"
[105] "ENSMUSG00000097102" "ENSMUSG00000101828" "ENSMUSG00000103674" "ENSMUSG00000087557"
[109] "ENSMUSG00000083192" "ENSMUSG00000084881" "ENSMUSG00000089979" "ENSMUSG00000091580"
[113] "ENSMUSG00000103861" "ENSMUSG00000096232" "ENSMUSG00000097465" "ENSMUSG00000091562"
[117] "ENSMUSG00000086899" "ENSMUSG00000022915" "ENSMUSG00000087282" "ENSMUSG00000101556"
[121] "ENSMUSG00000079662" "ENSMUSG00000073388" "ENSMUSG00000066944"

Add annotation to the results table

  • we have created an annotation table:
    • modified the column names
    • added median transcript length
    • dealt with the one-to-many issues for Entrez IDs.
load("../Course_Materials/Robjects/Ensembl_annotations.RData")
colnames(ensemblAnnot)
 [1] "GeneID"         "Entrez"         "Symbol"         "Description"    "Biotype"       
 [6] "Chr"            "Start"          "End"            "Strand"         "medianTxLength"
annotLvV <- as.data.frame(resLvV) %>% 
    rownames_to_column("GeneID") %>% 
    left_join(ensemblAnnot, "GeneID") %>% 
    rename(logFC=log2FoldChange, FDR=padj)

Finally we can output the annotation DE results using write_tsv.

write_tsv(annotLvV, "../Course_Materials/data/VirginVsLactating_Results_Annotated.txt")

have to a look and see if genes make biological sense

annotLvV %>%
    arrange(FDR) %>%
    head(10)

Visualisation

ddsShrink <- lfcShrink(ddsObj, coef="Status_lactate_vs_virgin")
shrinkLvV <- as.data.frame(ddsShrink) %>%
    rownames_to_column("GeneID") %>% 
    left_join(ensemblAnnot, "GeneID") %>% 
    rename(logFC=log2FoldChange, FDR=padj)

P-value histogram

A quick and easy “sanity check” for our DE results is to generate a p-value histogram. REfer to Oscar’s lecture yesterday. What we should see is a high bar in the 0 - 0.05 and then a roughly uniform tail to the right of this.

hist(shrinkLvV$pvalue)

MA plots

MA plots are a common way to visualize the results of a differential analysis. We met them briefly towards the end of Session 2 yesterday.

This plot shows the log-Fold Change against expression but remember its a mean across all the samples

DESeq2 has a handy function for plotting this…

plotMA(ddsShrink, alpha=0.05)

A Brief Introduction to ggplot2

In brief:-

  • shrinkLvV is our data frame containing the variables we wish to plot
  • aes creates a mapping between the variables in our data frame to the aesthetic proprties of the plot:
    • the x-axis will be mapped to log2(baseMean)
    • the y-axis will be mapped to the logFC
  • geom_point specifies the particular type of plot we want (in this case a scatter plot)
  • geom_text allows us to add labels to some or all of the points

we can add metadata from the sampleinfo table to the data. The colours are automatically chosen by ggplot2, but we can specifiy particular values if we want.

Say we want to add top 10 most sig expressed genes to graph at labels, simplest way is to make an extra column with just those values in.

GO RIDICULOUSLY SLOWLY they are layers

# add a column with the names of only the top 10 genes
cutoff <- sort(shrinkLvV$pvalue)[10]
shrinkLvV <- shrinkLvV %>% 
    mutate(TopGeneLabel=ifelse(pvalue<=cutoff, Symbol, ""))
ggplot(shrinkLvV, aes(x = log2(baseMean), y=logFC)) + 
    geom_point(aes(colour=FDR < 0.05), shape=20, size=0.5) +
    geom_text(aes(label=TopGeneLabel)) +
    labs(x="mean of normalised counts", y="log fold change")

Volcano plot

Another common visualisation is the volcano plot which displays a measure of significance on the y-axis and fold-change on the x-axis.

Challenge 2

Use the log2 fold change (logFC) on the x-axis, and use -log10(FDR) on the y-axis. (This >-log10 transformation is commonly used for p-values as it means that more significant genes have a >higher scale)

  1. Create a column of -log10(FDR) values

  2. Create a plot with points coloured by if FDR < 0.05

An example of what your plot should look like:

# first remove the filtered genes (FDR=NA) and create a -log10(FDR) column
filtTab <- shrinkLvV %>% 
    filter(!is.na(FDR)) %>% 
    mutate(`-log10(FDR)` = -log10(FDR))
ggplot(filtTab, aes(x = logFC, y=`-log10(FDR)`)) + 
    geom_point(aes(colour=FDR < 0.05), size=1)

Strip chart for gene expression

to do a sanity check and look at a specific gene, we can quickly look at grouped expression by using plotCounts function of DESeq2 to retrieve the normalised expression values from the ddsObj object and then plotting with ggplot2.

Show reduced objects as you go along

plotcounts is a function from deseq2 that we can repurpose to pull out the normalised expression for a particular gene.

the expand limits bit is to expand the axis to make sure you include 0

# Let's look at the most significantly differentially expressed gene
topgene <- filter(shrinkLvV, Symbol=="Wap")
geneID <- topgene$GeneID
plotCounts(ddsObj, gene = geneID, intgroup = c("CellType", "Status"),
           returnData = T) %>% 
    ggplot(aes(x=Status, y=log2(count))) +
      geom_point(aes(fill=Status), shape=21, size=2) +
      facet_wrap(~CellType) +
      expand_limits(y=0)

Interactive StripChart with Glimma

An interactive version of the volcano plot above that includes the raw per sample values in a separate panel is possible via the glXYPlot function in the Glimma package.

library(Glimma)

group <- str_remove_all(sampleinfo$Group, "[aeiou]")

de <- shrinkLvV$FDR <= 0.05 & !is.na(shrinkLvV$FDR)

normCounts <- log2(counts(ddsObj))

glXYPlot(
  x = shrinkLvV$logFC,
  y = -log10(shrinkLvV$pvalue),
  xlab = "logFC",
  ylab = "FDR",
  main = "Lactating v Virgin",
  counts = normCounts,
  groups = group,
  status = de,
  anno = shrinkLvV[, c("GeneID", "Symbol", "Description")],
  folder = "volcano"
)

This function creates an html page (./volcano/XY-Plot.html) with a volcano plot

Heatmap

We’re going to use the package ComplexHeatmap [@Gu2016]. We’ll also use circlize to generate a colour scale [@Gu2014].

library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 1.18.1
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://bioconductor.org/packages/ComplexHeatmap/

If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional 
  genomic data. Bioinformatics 2016.
========================================
library(circlize)
========================================
circlize version 0.4.4
CRAN page: https://cran.r-project.org/package=circlize
Github page: https://github.com/jokergoo/circlize
Documentation: http://jokergoo.github.io/circlize_book/book/

If you use it in published research, please cite:
Gu, Z. circlize implements and enhances circular visualization 
  in R. Bioinformatics 2014.
========================================

We can’t plot the entire data set, let’s just select the top 150 by FDR. We’ll also z-transform the counts.

wt means weight, - for reverse order

Ash mentioned rlog yesterday, use vst today both avaliable with deseq2, best to check manual to get exact differences for plotting the differences are subtle so just use vst because its faster

# get the top genes
sigGenes <- as.data.frame(shrinkLvV) %>% 
    top_n(150, wt=-FDR) %>% 
    pull("GeneID")
# filter the data for the top 200 by padj in the LRT test
plotDat <- vst(ddsObj)[sigGenes,] %>% 
    assay()
z.mat <- t(scale(t(plotDat), center=TRUE, scale=TRUE))
  • assay pulls out the counts
  • z.mat the z-score matrix
  • yesterday we used heatmap2 which calculates these automatically for us, for complex heatmap we do it manually
  • z- score is fc centred around zero and scaled
  • the funtion ‘scale’ creates the z score for us but it expects data in the opposite orientation so we have to do a couple of transformations to make it work.

skew the scale for us, limits everything outside the myRamp to the truest colour so the small numbers in the middle don’t just end up white with no difference.

# colour palette
myPalette <- c("red3", "ivory", "blue3")
myRamp = colorRamp2(c(-2, 0, 2), myPalette)
Heatmap(z.mat, name = "z-score",
        col = myRamp,            
        show_row_names = FALSE,
        cluster_columns = FALSE)

we can also split the heat map into clusters and add some annotation.

hclust generates the same tree we see on the left of our heatmap.

we have to decide at which level we want to cut the tree, 1 is lowest level

ha1 where we get annotation from

rect_gp is grey rectangle around each block lwt is line weight

# cluster the data and split the tree
hcDat <- hclust(dist(z.mat))
cutGroups <- cutree(hcDat, h=4)
ha1 = HeatmapAnnotation(df = colData(ddsObj)[,c("CellType", "Status")])
Heatmap(z.mat, name = "z-score",
        col = myRamp,            
        show_row_name = FALSE,
        cluster_columns = FALSE,
        split=cutGroups,
        rect_gp = gpar(col = "darkgrey", lwd=0.5),
        top_annotation = ha1)

save(annotLvV, shrinkLvV, file="../Course_Materials/results/Annotated_Results_LvV.RData")

Additional Material

There is additional material for you to work through in the Supplementary Materials directory. Details include using genomic ranges, retrieving gene models, exporting browser tracks and some extra useful plots like the one below.


References

LS0tCnRpdGxlOiAiUk5BLXNlcSBBbmFseXNpcyBpbiBSIgpzdWJ0aXRsZTogIkFubm90YXRpb24gYW5kIFZpc3VhbGlzYXRpb24gb2YgUk5BLXNlcSByZXN1bHRzIgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIE1hcmsgRHVubmluZywgQWJiaSBFZHdhcmRzLCBPc2NhciBSdWVkYSwgQXNobGV5IFNhd2xlIgpkYXRlOiAnYHIgZm9ybWF0KFN5cy50aW1lKCksICJMYXN0IG1vZGlmaWVkOiAlZCAlYiAlWSIpYCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwptaW51dGVzOiAzMDAKbGF5b3V0OiBwYWdlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCi0tLQoKUkVNRU1CRVIgVE8gQ09NTUVOVCBBUyBZT1UgR08hISEhISEhIQpTRVRXRCBXSVRIIEdVSQoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoYmlvbWFSdCkKbGlicmFyeShERVNlcTIpCmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCkJlZm9yZSBzdGFydGluZyB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgbWFrZSBzdXJlIHdlIGhhdmUgYWxsIHRoZSByZWxldmFudCBvYmplY3RzCmZyb20gdGhlIERpZmZlcmVudGlhbCBFeHByZXNzaW9uIGFuYWx5c2lzLgoKYGBge3IgbG9hZERhdGF9CmxvYWQoIi4uL0NvdXJzZV9NYXRlcmlhbHMvUm9iamVjdHMvREUuUkRhdGEiKQpgYGAKCiMgT3ZlcnZpZXcKCi0gQW5ub3RhdGlvbgotIFZpc3VhbGlzaW5nIERFIHJlc3VsdHMKCiMgQWRkaW5nIGFubm90YXRpb24gdG8gdGhlIERFU2VxMiByZXN1bHRzCgpWSUVXIHJlc0x2Vgpvbmx5IHNlZSB0aGUgRW5zZW1ibCBHZW5lIElELCB3aGljaCBpcyBub3QgdmVyeSBpbmZvcm1hdGl2ZS4gCgpUaGVyZSBhcmUgYSBudW1iZXIgb2Ygd2F5cyB0byBhZGQgYW5ub3RhdGlvbi4gVGhlIGFyZSBSIHBhY2thZ2VzIGZvciB0aGlzIGF0IGFuIG9yZ2FuaXNtIGxldmVsIHdoaWNoIGFyZSB1cGRhdGVkIGV2ZXJ5IDYgbW9udGhzLgoKQW4gYWx0ZXJuYXRpdmUgYXBwcm9hY2ggaXMgdG8gdXNlIGBiaW9tYVJ0YCwgYW4gaW50ZXJmYWNlIHRvIHRoZSAKW0Jpb01hcnRdKGh0dHA6Ly93d3cuYmlvbWFydC5vcmcvKSByZXNvdXJjZS4gVGhpcyBpcyB0aGUgbWV0aG9kIHdlIHdpbGwgdXNlIAp0b2RheS4KCiMjIFNlbGVjdCBCaW9NYXJ0IGRhdGFiYXNlIGFuZCBkYXRhc2V0CgpUaGUgZmlyc3Qgc3RlcCBpcyB0byBzZWxlY3QgdGhlIEJpb21hcnQgZGF0YWJhc2Ugd2UgYXJlIGdvaW5nIHRvIGFjY2VzcyBhbmQgCndoaWNoIGRhdGEgc2V0IHdlIGFyZSBnb2luZyB0byB1c2UuCgpFeHBsYWluIDQgbWFydHMKCmBgYHtyIGNvbm5lY3R9CiMgdmlldyB0aGUgYXZhaWxhYmxlIGRhdGFiYXNlcwpsaXN0TWFydHMoKQojIyBzZXQgdXAgY29ubmVjdGlvbiB0byBlbnNlbWJsIGRhdGFiYXNlCmVuc2VtYmw9dXNlTWFydCgiRU5TRU1CTF9NQVJUX0VOU0VNQkwiKQoKIyBsaXN0IHRoZSBhdmFpbGFibGUgZGF0YXNldHMgKHNwZWNpZXMpCmxpc3REYXRhc2V0cyhlbnNlbWJsKSAlPiUgCiAgICBmaWx0ZXIoc3RyX2RldGVjdChkZXNjcmlwdGlvbiwgIk1vdXNlIikpCgojIHNwZWNpZnkgYSBkYXRhIHNldCB0byB1c2UKZW5zZW1ibCA9IHVzZURhdGFzZXQoIm1tdXNjdWx1c19nZW5lX2Vuc2VtYmwiLCBtYXJ0PWVuc2VtYmwpCmBgYAoKIyMgUXVlcnkgdGhlIGRhdGFiYXNlCiBzZXQgdXAgcXVlcnkKIAoqICoqZmlsdGVycyoqIC0gd2hhdCB3ZSBzZWFyY2ggd2l0aCAtIEVuc2VtYmwgR2VuZSBJRHMKKiAqKnZhbHVlcyoqIC0gd2hlcmUgdGhleSBhcmUgLSB0aGUgRW5zZW1ibCBHZW5lIElEcyBmcm9tIG91ciBERSByZXN1bHRzIHRhYmxlCiogKiphdHRyaWJ1dGVzKiogLSB3aGF0IHdlIHdhbnQgYmFjawoKKiB0ZXN0IHlvdXIgcXVlcnkgb24gYSBzbWFsbCBsaXN0IGFzIGl0IHRha2VzIGEgd2hpbGUgZm9yIHRoZSB3aG9sZSBsb3QKCmBgYHtyIHF1ZXJ5QmlvTWFydCwgbWVzc2FnZT1GfQoKIyBjaGVjayB0aGUgYXZhaWxhYmxlICJmaWx0ZXJzIiAtIHRoaW5ncyB5b3UgY2FuIGZpbHRlciBmb3IKbGlzdEZpbHRlcnMoZW5zZW1ibCkgJT4lIAogICAgZmlsdGVyKHN0cl9kZXRlY3QobmFtZSwgImVuc2VtYmwiKSkKIyBTZXQgdGhlIGZpbHRlciB0eXBlIGFuZCB2YWx1ZXMKb3VyRmlsdGVyVHlwZSA8LSAiZW5zZW1ibF9nZW5lX2lkIgpmaWx0ZXJWYWx1ZXMgPC0gcm93bmFtZXMocmVzTHZWKVsxOjEwMDBdCgojIGNoZWNrIHRoZSBhdmFpbGFibGUgImF0dHJpYnV0ZXMiIC0gdGhpbmdzIHlvdSBjYW4gcmV0cmVpdmUKbGlzdEF0dHJpYnV0ZXMoZW5zZW1ibCkgJT4lIAogICAgaGVhZCgyMCkKIyBTZXQgdGhlIGxpc3Qgb2YgYXR0cmlidXRlcwphdHRyaWJ1dGVOYW1lcyA8LSBjKCdlbnNlbWJsX2dlbmVfaWQnLCAnZW50cmV6Z2VuZScsICdleHRlcm5hbF9nZW5lX25hbWUnKQoKIyBydW4gdGhlIHF1ZXJ5CmFubm90IDwtIGdldEJNKGF0dHJpYnV0ZXM9YXR0cmlidXRlTmFtZXMsIAogICAgICAgICAgICAgICBmaWx0ZXJzID0gb3VyRmlsdGVyVHlwZSwgCiAgICAgICAgICAgICAgIHZhbHVlcyA9IGZpbHRlclZhbHVlcywgCiAgICAgICAgICAgICAgIG1hcnQgPSBlbnNlbWJsKQpgYGAKCgojIyMgT25lLXRvLW1hbnkgcmVsYXRpb25zaGlwcwoKTGV0J3MgaW5zcGVjdCB0aGUgYW5ub3RhdGlvbi4KCmBgYHtyIGluc3BlY3RBbm5vdH0KaGVhZChhbm5vdCkKZGltKGFubm90KSAjIHdoeSBhcmUgdGhlcmUgbW9yZSB0aGFuIDEwMDAgcm93cz8KbGVuZ3RoKHVuaXF1ZShhbm5vdCRlbnNlbWJsX2dlbmVfaWQpKSAjIHdoeSBhcmUgdGhlcmUgbGVzcyB0aGFuIDEwMDAgR2VuZSBpZHM/Cgppc0R1cCA8LSBkdXBsaWNhdGVkKGFubm90JGVuc2VtYmxfZ2VuZV9pZCkKZHVwIDwtIGFubm90JGVuc2VtYmxfZ2VuZV9pZFtpc0R1cF0KYW5ub3RbYW5ub3QkZW5zZW1ibF9nZW5lX2lkJWluJWR1cCxdCmBgYAoKbWlzc2luZyBvbmUgaXMgZGVwcmVjZWF0ZWQgZ2VuZSBhbm5vdGF0aW9uLi4uIChvdXIgZ3RmIGlzIGxpdHRsZSBvbGRlciB0aGFuIGJpb21hUnQpCgpUaGVyZSBhcmUgYSBjb3VwbGUgb2YgZ2VuZXMgdGhhdCBoYXZlIG11bHRpcGxlIGVudHJpZXMgaW4gdGhlIHJldHJpZXZlZCAKYW5ub3RhdGlvbi4gVGhpcyBpcyBiZWNhdWVzIHRoZXJlIGFyZSBtdWx0aXBsZSBFbnRyZXogSURzIGZvciBhIHNpbmdsZSBFbnNlbWJsIApnZW5lLiBUaGVzZSBvbmUtdG8tbWFueSByZWxhdGlvbnNoaXBzIGNvbWUgdXAgZnJlcXVlbnRseSBpbiBnZW5vbWljIGRhdGFiYXNlcywgCml0IGlzIGltcG9ydGFudCB0byBiZSBhd2FyZSBvZiB0aGVtIGFuZCBjaGVjayB3aGVuIG5lY2Vzc2FyeS4gCgpXZSB3aWxsIG5lZWQgdG8gZG8gYSBsaXR0bGUgd29yayBiZWZvcmUgYWRkaW5nIHRoZSBhbm5vdGF0aW9uIHRvIG91dCByZXN1bHRzIAp0YWJsZS4gV2UgY291bGQgZGVjaWRlIHRvIGRpc2NhcmQgb25lIG9yIGJvdGggb2YgdGhlIEVudHJleiBJRCBtYXBwaW5ncywgb3Igd2UgCmNvdWxkIGNvbmNhdGVuYXRlIHRoZSBFbnRyZXogSURzIHNvIHRoYXQgd2UgZG9uJ3QgbG9zZSBpbmZvcm1hdGlvbi4gCgp0aGlzIGlsbHVzdHJhdGVzIGhvdy93aHkgYW5ub3RhdGlvbiBpcyBjb21wbGljYXRlZCBhbmQgZGlmZmljdWx0CgojIyBSZXRyaWV2ZSBmdWxsIGFubm90YXRpb24KCj4gIyMjIENoYWxsZW5nZSAxIHsuY2hhbGxlbmdlfQo+IFRoYXQgd2FzIGp1c3QgMTAwMCBnZW5lcy4gV2UgbmVlZCBhbm5vdGF0aW9ucyBmb3IgdGhlIGVudGlyZSByZXN1bHRzIHRhYmxlLgo+IEFsc28sIHRoZXJlIG1heSBiZSBzb21lIG90aGVyIGludGVyZXN0aW5nIGNvbHVtbnMgaW4gQmlvTWFydCB0aGF0IHdlIHdpc2ggdG8KPiByZXRyaWV2ZS4gIAo+Cj4gKGEpIFNlYXJjaCB0aGUgYXR0cmlidXRlcyBhbmQgYWRkIHRoZSBmb2xsb3dpbmcgdG8gb3VyIGxpc3Qgb2YgYXR0cmlidXRlczogIAo+ICAgICAgIChpKSBUaGUgZ2VuZSBkZXNjcmlwdGlvbiAgIAo+ICAgICAgIChpaSkgVGhlIGdlbmUgYmlvdHlwZSAgCj4gKGIpIFF1ZXJ5IEJpb01hcnQgdXNpbmcgYWxsIG9mIHRoZSBnZW5lcyBpbiBvdXIgcmVzdWx0cyB0YWJsZSAoYHJlc0x2VmApICAKPgo+IChjKSBIb3cgbWFueSBFbnNlbWJsIGdlbmVzIGhhdmUgbXVsdGlwZSBFbnRyZXogSURzIGFzc29jaWF0ZWQgd2l0aCB0aGVtPyAgCj4gKGQpIEhvdyBtYW55IEVuc2VtYmwgZ2VuZXMgaW4gYHJlc0x2VmAgZG9uJ3QgaGF2ZSBhbnkgYW5ub3RhdGlvbj8gV2h5IGlzIHRoaXM/CgpgYGB7ciBzb2x1dGlvbkNoYWxsZW5nZTF9CmZpbHRlclZhbHVlcyA8LSByb3duYW1lcyhyZXNMdlYpCgojIGNoZWNrIHRoZSBhdmFpbGFibGUgImF0dHJpYnV0ZXMiIC0gdGhpbmdzIHlvdSBjYW4gcmV0cmVpdmUKbGlzdEF0dHJpYnV0ZXMoZW5zZW1ibCkgJT4lCiAgICBoZWFkKDIwKQphdHRyaWJ1dGVOYW1lcyA8LSBjKCdlbnNlbWJsX2dlbmVfaWQnLAogICAgICAgICAgICAgICAgICAgICdlbnRyZXpnZW5lJywKICAgICAgICAgICAgICAgICAgICAnZXh0ZXJuYWxfZ2VuZV9uYW1lJywKICAgICAgICAgICAgICAgICAgICAnZGVzY3JpcHRpb24nLAogICAgICAgICAgICAgICAgICAgICdnZW5lX2Jpb3R5cGUnKQoKIyBydW4gdGhlIHF1ZXJ5CmFubm90IDwtIGdldEJNKGF0dHJpYnV0ZXM9YXR0cmlidXRlTmFtZXMsCiAgICAgICAgICAgICAgIGZpbHRlcnMgPSBvdXJGaWx0ZXJUeXBlLAogICAgICAgICAgICAgICB2YWx1ZXMgPSBmaWx0ZXJWYWx1ZXMsCiAgICAgICAgICAgICAgIG1hcnQgPSBlbnNlbWJsKQoKIyBkdWxpY2F0ZSBpZHMKc3VtKGR1cGxpY2F0ZWQoYW5ub3QkZW5zZW1ibF9nZW5lX2lkKSkKCiMgbWlzc2luZyBnZW5zCm1pc3NpbmdHZW5lcyA8LSAhcm93bmFtZXMocmVzTHZWKSVpbiVhbm5vdCRlbnNlbWJsX2dlbmVfaWQKcm93bmFtZXMocmVzTHZWKVttaXNzaW5nR2VuZXNdCmBgYAoKIyMjIEFkZCBhbm5vdGF0aW9uIHRvIHRoZSByZXN1bHRzIHRhYmxlCgoqIHdlIGhhdmUgY3JlYXRlZCBhbiBhbm5vdGF0aW9uIHRhYmxlOgogICAgKiBtb2RpZmllZCB0aGUgY29sdW1uIG5hbWVzCiAgICAqIGFkZGVkIG1lZGlhbiB0cmFuc2NyaXB0IGxlbmd0aAogICAgKiBkZWFsdCB3aXRoIHRoZSBvbmUtdG8tbWFueSBpc3N1ZXMgZm9yIEVudHJleiBJRHMuCgoKYGBge3IgYWRkQW5ub3RhdGlvbiwgbWVzc2FnZT1GQUxTRX0KbG9hZCgiLi4vQ291cnNlX01hdGVyaWFscy9Sb2JqZWN0cy9FbnNlbWJsX2Fubm90YXRpb25zLlJEYXRhIikKY29sbmFtZXMoZW5zZW1ibEFubm90KQphbm5vdEx2ViA8LSBhcy5kYXRhLmZyYW1lKHJlc0x2VikgJT4lIAogICAgcm93bmFtZXNfdG9fY29sdW1uKCJHZW5lSUQiKSAlPiUgCiAgICBsZWZ0X2pvaW4oZW5zZW1ibEFubm90LCAiR2VuZUlEIikgJT4lIAogICAgcmVuYW1lKGxvZ0ZDPWxvZzJGb2xkQ2hhbmdlLCBGRFI9cGFkaikKYGBgCgpGaW5hbGx5IHdlIGNhbiBvdXRwdXQgdGhlIGFubm90YXRpb24gREUgcmVzdWx0cyB1c2luZyBgd3JpdGVfdHN2YC4KCmBgYHtyIG91dHB1dERFdGFibGVzLCBldmFsPUZ9CndyaXRlX3Rzdihhbm5vdEx2ViwgIi4uL0NvdXJzZV9NYXRlcmlhbHMvZGF0YS9WaXJnaW5Wc0xhY3RhdGluZ19SZXN1bHRzX0Fubm90YXRlZC50eHQiKQpgYGAKCmhhdmUgdG8gYSBsb29rIGFuZCBzZWUgaWYgZ2VuZXMgbWFrZSBiaW9sb2dpY2FsIHNlbnNlCmBgYHtyIHF1aWNrTG9va30KYW5ub3RMdlYgJT4lCiAgICBhcnJhbmdlKEZEUikgJT4lCiAgICBoZWFkKDEwKQpgYGAKCgojIFZpc3VhbGlzYXRpb24KCiogYERFU2VxMmAgcHJvdmlkZXMgYSBmdW5jdG9uIGNhbGxlZCBgbGZjU2hyaW5rYCB0aGF0IHNocmlua3MgbG9nLUZvbGQgQ2hhbmdlIAooTEZDKSBlc3RpbWF0ZXMgdG93YXJkcyB6ZXJvICAKKiBnZW5lcyB3aXRoIGxvdyBjb3VudHMgYW5kIGhpZ2ggRkMgYXBwZWFyIGJlIG11Y2ggbW9yZSBzaWduaWZpY2FudCB0aGFuIHRoZXkgYXJlLgoqIFRoZSBgbGZjU2hyaW5rYCBtZXRob2QKY29tcGVuc2F0ZXMgZm9yIHRoaXMgYW5kIGFsbG93cyBiZXR0ZXIgdmlzdWFsaXNhdGlvbiBhbmQgcmFua2luZyBvZiBnZW5lcy4gCgpgYGB7ciBzaHJpbmtMRkN9CmRkc1NocmluayA8LSBsZmNTaHJpbmsoZGRzT2JqLCBjb2VmPSJTdGF0dXNfbGFjdGF0ZV92c192aXJnaW4iKQpzaHJpbmtMdlYgPC0gYXMuZGF0YS5mcmFtZShkZHNTaHJpbmspICU+JQogICAgcm93bmFtZXNfdG9fY29sdW1uKCJHZW5lSUQiKSAlPiUgCiAgICBsZWZ0X2pvaW4oZW5zZW1ibEFubm90LCAiR2VuZUlEIikgJT4lIAogICAgcmVuYW1lKGxvZ0ZDPWxvZzJGb2xkQ2hhbmdlLCBGRFI9cGFkaikKYGBgCgojIyBQLXZhbHVlIGhpc3RvZ3JhbQoKQSBxdWljayBhbmQgZWFzeSAic2FuaXR5IGNoZWNrIiBmb3Igb3VyIERFIHJlc3VsdHMgaXMgdG8gZ2VuZXJhdGUgYSBwLXZhbHVlIApoaXN0b2dyYW0uIFJFZmVyIHRvIE9zY2FyJ3MgbGVjdHVyZSB5ZXN0ZXJkYXkuIFdoYXQgd2Ugc2hvdWxkIHNlZSBpcyBhIGhpZ2ggYmFyIGluIHRoZSBgMCAtIDAuMDVgIGFuZCB0aGVuIGEgcm91Z2hseSB1bmlmb3JtIHRhaWwgdG8gdGhlIHJpZ2h0IG9mIHRoaXMuCgpgYGB7ciBwdmFsSGlzdCwgZmlnLmFsaWduPSJjZW50ZXIifQpoaXN0KHNocmlua0x2ViRwdmFsdWUpCmBgYAoKIyMgTUEgcGxvdHMKCk1BIHBsb3RzIGFyZSBhIGNvbW1vbiB3YXkgdG8gdmlzdWFsaXplIHRoZSByZXN1bHRzIG9mIGEgZGlmZmVyZW50aWFsIGFuYWx5c2lzLiAKV2UgbWV0IHRoZW0gYnJpZWZseSB0b3dhcmRzIHRoZSBlbmQgb2YgW1Nlc3Npb24gCjJdKDAyX1ByZXByb2Nlc3NpbmdfRGF0YS5uYi5odG1sKSB5ZXN0ZXJkYXkuCgpUaGlzIHBsb3Qgc2hvd3MgdGhlIGxvZy1Gb2xkIENoYW5nZSBhZ2FpbnN0IGV4cHJlc3Npb24gYnV0IHJlbWVtYmVyIGl0cyBhIG1lYW4gYWNyb3NzIGFsbCB0aGUgc2FtcGxlcwoKYERFU2VxMmAgaGFzIGEgaGFuZHkgZnVuY3Rpb24gZm9yIHBsb3R0aW5nIHRoaXMuLi4KCmBgYHtyIG1hUGxvdERFU2VxMiwgZmlnLmFsaWduPSJjZW50ZXIiLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQpwbG90TUEoZGRzU2hyaW5rLCBhbHBoYT0wLjA1KQpgYGAKCgojIyMgQSBCcmllZiBJbnRyb2R1Y3Rpb24gdG8gYGdncGxvdDJgCgoKSW4gYnJpZWY6LQoKLSBgc2hyaW5rTHZWYCBpcyBvdXIgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSB2YXJpYWJsZXMgd2Ugd2lzaCB0byBwbG90Ci0gYGFlc2AgY3JlYXRlcyBhIG1hcHBpbmcgYmV0d2VlbiB0aGUgdmFyaWFibGVzIGluIG91ciBkYXRhIGZyYW1lIHRvIHRoZSAKKmFlcyp0aGV0aWMgcHJvcHJ0aWVzIG9mIHRoZSBwbG90OgogICAgKyB0aGUgeC1heGlzIHdpbGwgYmUgbWFwcGVkIHRvIGxvZzIoYGJhc2VNZWFuYCkKICAgICsgdGhlIHktYXhpcyB3aWxsIGJlIG1hcHBlZCB0byB0aGUgYGxvZ0ZDYAotIGBnZW9tX3BvaW50YCBzcGVjaWZpZXMgdGhlIHBhcnRpY3VsYXIgdHlwZSBvZiBwbG90IHdlIHdhbnQgKGluIHRoaXMgY2FzZSBhIHNjYXR0ZXIgCnBsb3QpCi0gYGdlb21fdGV4dGAgYWxsb3dzIHVzIHRvIGFkZCBsYWJlbHMgdG8gc29tZSBvciBhbGwgb2YgdGhlIHBvaW50cwogICAgKyBzZWUgCiAgICBbdGhlIGNoZWF0c2hlZXRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAzL2dncGxvdDItY2hlYXRzaGVldC5wZGYpIAogICAgZm9yIG90aGVyIHBsb3QgdHlwZXMKCndlIGNhbiBhZGQgbWV0YWRhdGEgZnJvbSB0aGUgYHNhbXBsZWluZm9gIHRhYmxlIHRvIHRoZSBkYXRhLiBUaGUgY29sb3VycyBhcmUgYXV0b21hdGljYWxseSBjaG9zZW4gYnkKYGdncGxvdDJgLCBidXQgd2UgY2FuIHNwZWNpZml5IHBhcnRpY3VsYXIgdmFsdWVzIGlmIHdlIHdhbnQuCgpTYXkgd2Ugd2FudCB0byBhZGQgdG9wIDEwIG1vc3Qgc2lnIGV4cHJlc3NlZCBnZW5lcyB0byBncmFwaCBhdCBsYWJlbHMsIHNpbXBsZXN0IHdheSBpcyB0byBtYWtlIGFuIGV4dHJhIGNvbHVtbiB3aXRoIGp1c3QgdGhvc2UgdmFsdWVzIGluLgoKR08gUklESUNVTE9VU0xZIFNMT1dMWSAKdGhleSBhcmUgbGF5ZXJzCgpgYGB7ciBtYVBsb3QsIGZpZy5hbGlnbj0iY2VudGVyIiwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KIyBhZGQgYSBjb2x1bW4gd2l0aCB0aGUgbmFtZXMgb2Ygb25seSB0aGUgdG9wIDEwIGdlbmVzCmN1dG9mZiA8LSBzb3J0KHNocmlua0x2ViRwdmFsdWUpWzEwXQpzaHJpbmtMdlYgPC0gc2hyaW5rTHZWICU+JSAKICAgIG11dGF0ZShUb3BHZW5lTGFiZWw9aWZlbHNlKHB2YWx1ZTw9Y3V0b2ZmLCBTeW1ib2wsICIiKSkKCmdncGxvdChzaHJpbmtMdlYsIGFlcyh4ID0gbG9nMihiYXNlTWVhbiksIHk9bG9nRkMpKSArIAogICAgZ2VvbV9wb2ludChhZXMoY29sb3VyPUZEUiA8IDAuMDUpLCBzaGFwZT0yMCwgc2l6ZT0wLjUpICsKICAgIGdlb21fdGV4dChhZXMobGFiZWw9VG9wR2VuZUxhYmVsKSkgKwogICAgbGFicyh4PSJtZWFuIG9mIG5vcm1hbGlzZWQgY291bnRzIiwgeT0ibG9nIGZvbGQgY2hhbmdlIikKYGBgCgojIyBWb2xjYW5vIHBsb3QKQW5vdGhlciBjb21tb24gdmlzdWFsaXNhdGlvbiBpcyB0aGUgClsqdm9sY2FubyBwbG90Kl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvVm9sY2Fub19wbG90XyhzdGF0aXN0aWNzKSkgd2hpY2ggCmRpc3BsYXlzIGEgbWVhc3VyZSBvZiBzaWduaWZpY2FuY2Ugb24gdGhlIHktYXhpcyBhbmQgZm9sZC1jaGFuZ2Ugb24gdGhlIHgtYXhpcy4KCj4gIyMjIENoYWxsZW5nZSAyIHsuY2hhbGxlbmdlfQo+IFVzZSB0aGUgbG9nMiBmb2xkIGNoYW5nZSAoYGxvZ0ZDYCkgb24gdGhlIHgtYXhpcywgYW5kIHVzZSBgLWxvZzEwKEZEUilgIG9uIHRoZSB5LWF4aXMuIChUaGlzID5gLWxvZzEwYCB0cmFuc2Zvcm1hdGlvbiBpcyBjb21tb25seSB1c2VkIGZvciBwLXZhbHVlcyBhcyBpdCBtZWFucyB0aGF0IG1vcmUgc2lnbmlmaWNhbnQgZ2VuZXMgaGF2ZSBhID5oaWdoZXIgc2NhbGUpIAo+Cj4gKGEpIENyZWF0ZSBhIGNvbHVtbiBvZiAtbG9nMTAoRkRSKSB2YWx1ZXMKPgo+IChiKSBDcmVhdGUgYSBwbG90IHdpdGggcG9pbnRzIGNvbG91cmVkIGJ5IGlmIEZEUiA8IDAuMDUKCkFuIGV4YW1wbGUgb2Ygd2hhdCB5b3VyIHBsb3Qgc2hvdWxkIGxvb2sgbGlrZToKIVtdKC4uL2ltYWdlcy9Wb2xjYW5vLnBuZykgCgpgYGB7ciB2b2xjYW5vUGxvdCwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9N30KIyBmaXJzdCByZW1vdmUgdGhlIGZpbHRlcmVkIGdlbmVzIChGRFI9TkEpIGFuZCBjcmVhdGUgYSAtbG9nMTAoRkRSKSBjb2x1bW4KZmlsdFRhYiA8LSBzaHJpbmtMdlYgJT4lIAogICAgZmlsdGVyKCFpcy5uYShGRFIpKSAlPiUgCiAgICBtdXRhdGUoYC1sb2cxMChGRFIpYCA9IC1sb2cxMChGRFIpKQoKZ2dwbG90KGZpbHRUYWIsIGFlcyh4ID0gbG9nRkMsIHk9YC1sb2cxMChGRFIpYCkpICsgCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9RkRSIDwgMC4wNSksIHNpemU9MSkKYGBgCgojIyBTdHJpcCBjaGFydCBmb3IgZ2VuZSBleHByZXNzaW9uCgp0byBkbyBhIHNhbml0eSBjaGVjayBhbmQgbG9vayBhdCBhIHNwZWNpZmljIGdlbmUsIHdlIGNhbiBxdWlja2x5IGxvb2sgYXQgZ3JvdXBlZCBleHByZXNzaW9uIGJ5IHVzaW5nIGBwbG90Q291bnRzYCBmdW5jdGlvbiBvZiBgREVTZXEyYCB0byAgcmV0cmlldmUgdGhlIG5vcm1hbGlzZWQgZXhwcmVzc2lvbiB2YWx1ZXMgCmZyb20gdGhlIGBkZHNPYmpgIG9iamVjdCBhbmQgdGhlbiBwbG90dGluZyB3aXRoICBgZ2dwbG90MmAuCgoKU2hvdyByZWR1Y2VkIG9iamVjdHMgYXMgeW91IGdvIGFsb25nIAoKcGxvdGNvdW50cyBpcyBhIGZ1bmN0aW9uIGZyb20gZGVzZXEyIHRoYXQgd2UgY2FuIHJlcHVycG9zZSB0byBwdWxsIG91dCB0aGUgbm9ybWFsaXNlZCBleHByZXNzaW9uIGZvciBhIHBhcnRpY3VsYXIgZ2VuZS4KCnRoZSBleHBhbmQgbGltaXRzIGJpdCBpcyB0byBleHBhbmQgdGhlIGF4aXMgdG8gbWFrZSBzdXJlIHlvdSBpbmNsdWRlIDAKCmBgYHtyIHBsb3RHZW5lQ291bnRzfQojIExldCdzIGxvb2sgYXQgdGhlIG1vc3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZQp0b3BnZW5lIDwtIGZpbHRlcihzaHJpbmtMdlYsIFN5bWJvbD09IldhcCIpCmdlbmVJRCA8LSB0b3BnZW5lJEdlbmVJRAoKcGxvdENvdW50cyhkZHNPYmosIGdlbmUgPSBnZW5lSUQsIGludGdyb3VwID0gYygiQ2VsbFR5cGUiLCAiU3RhdHVzIiksCiAgICAgICAgICAgcmV0dXJuRGF0YSA9IFQpICU+JSAKICAgIGdncGxvdChhZXMoeD1TdGF0dXMsIHk9bG9nMihjb3VudCkpKSArCiAgICAgIGdlb21fcG9pbnQoYWVzKGZpbGw9U3RhdHVzKSwgc2hhcGU9MjEsIHNpemU9MikgKwogICAgICBmYWNldF93cmFwKH5DZWxsVHlwZSkgKwogICAgICBleHBhbmRfbGltaXRzKHk9MCkKYGBgCgoKIyMjIEludGVyYWN0aXZlIFN0cmlwQ2hhcnQgd2l0aCBHbGltbWEKCkFuIGludGVyYWN0aXZlIHZlcnNpb24gb2YgdGhlIHZvbGNhbm8gcGxvdCBhYm92ZSB0aGF0IGluY2x1ZGVzIHRoZSByYXcgcGVyIApzYW1wbGUgdmFsdWVzIGluIGEgc2VwYXJhdGUgcGFuZWwgaXMgcG9zc2libGUgdmlhIHRoZSBgZ2xYWVBsb3RgIGZ1bmN0aW9uIGluIHRoZQoqR2xpbW1hKiBwYWNrYWdlLgoKCmBgYHtyIEdsaW1tYSwgZXZhbD1GQUxTRX0KbGlicmFyeShHbGltbWEpCgpncm91cCA8LSBzdHJfcmVtb3ZlX2FsbChzYW1wbGVpbmZvJEdyb3VwLCAiW2FlaW91XSIpCgpkZSA8LSBzaHJpbmtMdlYkRkRSIDw9IDAuMDUgJiAhaXMubmEoc2hyaW5rTHZWJEZEUikKCm5vcm1Db3VudHMgPC0gbG9nMihjb3VudHMoZGRzT2JqKSkKCmdsWFlQbG90KAogIHggPSBzaHJpbmtMdlYkbG9nRkMsCiAgeSA9IC1sb2cxMChzaHJpbmtMdlYkcHZhbHVlKSwKICB4bGFiID0gImxvZ0ZDIiwKICB5bGFiID0gIkZEUiIsCiAgbWFpbiA9ICJMYWN0YXRpbmcgdiBWaXJnaW4iLAogIGNvdW50cyA9IG5vcm1Db3VudHMsCiAgZ3JvdXBzID0gZ3JvdXAsCiAgc3RhdHVzID0gZGUsCiAgYW5ubyA9IHNocmlua0x2VlssIGMoIkdlbmVJRCIsICJTeW1ib2wiLCAiRGVzY3JpcHRpb24iKV0sCiAgZm9sZGVyID0gInZvbGNhbm8iCikKYGBgCgpUaGlzIGZ1bmN0aW9uIGNyZWF0ZXMgYW4gaHRtbCBwYWdlICguL3ZvbGNhbm8vWFktUGxvdC5odG1sKSB3aXRoIGEgdm9sY2FubyBwbG90IAoKIyMgSGVhdG1hcAoKV2UncmUgZ29pbmcgdG8gdXNlIHRoZSBwYWNrYWdlIGBDb21wbGV4SGVhdG1hcGAgW0BHdTIwMTZdLiBXZSdsbCBhbHNvIHVzZQpgY2lyY2xpemVgIHRvIGdlbmVyYXRlIGEgY29sb3VyIHNjYWxlIFtAR3UyMDE0XS4KCmBgYHtyIGNvbXBsZXhIZWF0bWFwLCBtZXNzYWdlPUZ9CmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2lyY2xpemUpCmBgYAoKV2UgY2FuJ3QgcGxvdCB0aGUgZW50aXJlIGRhdGEgc2V0LCBsZXQncyBqdXN0IHNlbGVjdCB0aGUgdG9wIDE1MCBieSBGRFIuIFdlJ2xsCmFsc28gei10cmFuc2Zvcm0gdGhlIGNvdW50cy4KCnd0IG1lYW5zIHdlaWdodCwgLSBmb3IgcmV2ZXJzZSBvcmRlcgoKQXNoIG1lbnRpb25lZCBybG9nIHllc3RlcmRheSwgdXNlIHZzdCB0b2RheQpib3RoIGF2YWxpYWJsZSB3aXRoIGRlc2VxMiwgYmVzdCB0byBjaGVjayBtYW51YWwgdG8gZ2V0IGV4YWN0IGRpZmZlcmVuY2VzCmZvciBwbG90dGluZyB0aGUgZGlmZmVyZW5jZXMgYXJlIHN1YnRsZSBzbyBqdXN0IHVzZSB2c3QgYmVjYXVzZSBpdHMgZmFzdGVyCgpgYGB7ciBzZWxlY3RHZW5lc30KIyBnZXQgdGhlIHRvcCBnZW5lcwpzaWdHZW5lcyA8LSBhcy5kYXRhLmZyYW1lKHNocmlua0x2VikgJT4lIAogICAgdG9wX24oMTUwLCB3dD0tRkRSKSAlPiUgCiAgICBwdWxsKCJHZW5lSUQiKQoKIyBmaWx0ZXIgdGhlIGRhdGEgZm9yIHRoZSB0b3AgMjAwIGJ5IHBhZGogaW4gdGhlIExSVCB0ZXN0CnBsb3REYXQgPC0gdnN0KGRkc09iailbc2lnR2VuZXMsXSAlPiUgCiAgICBhc3NheSgpCnoubWF0IDwtIHQoc2NhbGUodChwbG90RGF0KSwgY2VudGVyPVRSVUUsIHNjYWxlPVRSVUUpKQpgYGAKCiogYXNzYXkgcHVsbHMgb3V0IHRoZSBjb3VudHMKKiB6Lm1hdCB0aGUgei1zY29yZSBtYXRyaXgKKiB5ZXN0ZXJkYXkgd2UgdXNlZCBoZWF0bWFwMiB3aGljaCBjYWxjdWxhdGVzIHRoZXNlIGF1dG9tYXRpY2FsbHkgZm9yIHVzLCBmb3IgY29tcGxleCBoZWF0bWFwIHdlIGRvIGl0IG1hbnVhbGx5Ciogei0gc2NvcmUgaXMgZmMgY2VudHJlZCBhcm91bmQgemVybyBhbmQgc2NhbGVkCiogdGhlIGZ1bnRpb24gJ3NjYWxlJyBjcmVhdGVzIHRoZSB6IHNjb3JlIGZvciB1cyBidXQgaXQgZXhwZWN0cyBkYXRhIGluIHRoZSBvcHBvc2l0ZSBvcmllbnRhdGlvbiBzbyB3ZSBoYXZlIHRvIGRvIGEgY291cGxlIG9mIHRyYW5zZm9ybWF0aW9ucyB0byBtYWtlIGl0IHdvcmsuCgoKc2tldyB0aGUgc2NhbGUgZm9yIHVzLCBsaW1pdHMgZXZlcnl0aGluZyBvdXRzaWRlIHRoZSBteVJhbXAgdG8gdGhlIHRydWVzdCBjb2xvdXIgc28gdGhlIHNtYWxsIG51bWJlcnMgaW4gdGhlIG1pZGRsZSBkb24ndCBqdXN0IGVuZCB1cCB3aGl0ZSB3aXRoIG5vIGRpZmZlcmVuY2UuCgpgYGB7ciBjb2xvdXJTY2FsZX0KIyBjb2xvdXIgcGFsZXR0ZQpteVBhbGV0dGUgPC0gYygicmVkMyIsICJpdm9yeSIsICJibHVlMyIpCm15UmFtcCA9IGNvbG9yUmFtcDIoYygtMiwgMCwgMiksIG15UGFsZXR0ZSkKYGBgCgpgYGB7ciBoZWF0bWFwLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD04fQpIZWF0bWFwKHoubWF0LCBuYW1lID0gInotc2NvcmUiLAogICAgICAgIGNvbCA9IG15UmFtcCwgICAgICAgICAgICAKICAgICAgICBzaG93X3Jvd19uYW1lcyA9IEZBTFNFLAogICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFKQpgYGAKCndlIGNhbiBhbHNvIHNwbGl0IHRoZSBoZWF0IG1hcCBpbnRvIGNsdXN0ZXJzIGFuZCBhZGQgc29tZSBhbm5vdGF0aW9uLgoKaGNsdXN0IGdlbmVyYXRlcyB0aGUgc2FtZSB0cmVlIHdlIHNlZSBvbiB0aGUgbGVmdCBvZiBvdXIgaGVhdG1hcC4KCndlIGhhdmUgdG8gZGVjaWRlIGF0IHdoaWNoIGxldmVsIHdlIHdhbnQgdG8gY3V0IHRoZSB0cmVlLCAxIGlzIGxvd2VzdCBsZXZlbAoKaGExIHdoZXJlIHdlIGdldCBhbm5vdGF0aW9uIGZyb20KCnJlY3RfZ3AgaXMgZ3JleSByZWN0YW5nbGUgYXJvdW5kIGVhY2ggYmxvY2sKbHd0IGlzIGxpbmUgd2VpZ2h0CmBgYHtyIHNwbGl0SGVhdG1hcCwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9OH0KIyBjbHVzdGVyIHRoZSBkYXRhIGFuZCBzcGxpdCB0aGUgdHJlZQpoY0RhdCA8LSBoY2x1c3QoZGlzdCh6Lm1hdCkpCmN1dEdyb3VwcyA8LSBjdXRyZWUoaGNEYXQsIGg9NCkKCmhhMSA9IEhlYXRtYXBBbm5vdGF0aW9uKGRmID0gY29sRGF0YShkZHNPYmopWyxjKCJDZWxsVHlwZSIsICJTdGF0dXMiKV0pCgpIZWF0bWFwKHoubWF0LCBuYW1lID0gInotc2NvcmUiLAogICAgICAgIGNvbCA9IG15UmFtcCwgICAgICAgICAgICAKICAgICAgICBzaG93X3Jvd19uYW1lID0gRkFMU0UsCiAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgICAgICAgc3BsaXQ9Y3V0R3JvdXBzLAogICAgICAgIHJlY3RfZ3AgPSBncGFyKGNvbCA9ICJkYXJrZ3JleSIsIGx3ZD0wLjUpLAogICAgICAgIHRvcF9hbm5vdGF0aW9uID0gaGExKQpgYGAKCgpgYGB7ciBzYXZlRW52aXJvbm1lbnQsIGV2YWw9RkFMU0V9CnNhdmUoYW5ub3RMdlYsIHNocmlua0x2ViwgZmlsZT0iLi4vQ291cnNlX01hdGVyaWFscy9yZXN1bHRzL0Fubm90YXRlZF9SZXN1bHRzX0x2Vi5SRGF0YSIpCmBgYAoKCiMgQWRkaXRpb25hbCBNYXRlcmlhbApUaGVyZSBpcyBhZGRpdGlvbmFsIG1hdGVyaWFsIGZvciB5b3UgdG8gd29yayB0aHJvdWdoIGluIHRoZSBbU3VwcGxlbWVudGFyeSBNYXRlcmlhbHNdKC4uL1N1cHBsZW1lbnRhcnlfTWF0ZXJpYWxzL1MzX0Fubm90YXRpb25fYW5kX1Zpc3VhbGlzYXRpb24ubmIuaHRtbCkgZGlyZWN0b3J5LiBEZXRhaWxzIGluY2x1ZGUgdXNpbmcgZ2Vub21pYyByYW5nZXMsIHJldHJpZXZpbmcgZ2VuZSBtb2RlbHMsIGV4cG9ydGluZyBicm93c2VyIHRyYWNrcyBhbmQgc29tZSBleHRyYSB1c2VmdWwgcGxvdHMgbGlrZSB0aGUgb25lIGJlbG93LgoKCiFbXSguLi9pbWFnZXMvQ292ZXJhZ2UucG5nKQoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFJlZmVyZW5jZXMK